查看原文
其他

.NET5 开发手机提词应用

DotNet 2021-12-16

The following article is from 杨中科 Author 杨中科

项目说明

我使用电脑录制视频教程的时候,会展示PPT给观众,同时也有一些提示性的文字给我自己看。这就类似于很多电视节目录制现场的“提词器”。
节目录制现场的提词器

在PC环境下,PowerPoint也具有提词器功能,在编辑PPT的时候,把每一页的备注中写上提示词即可。投影到屏幕上的给观众看的画面没有提示词,而演讲者的电脑屏幕的画面中有提示词。但是这要求使用投影仪或者使用双屏幕。

而我的视频录制环境是我和观众是看的同一块屏幕,因此无法使用PowerPoint的提词功能。所以我只能自己开发一个应用。       

既然我和观众是看的同一块屏幕,如果想达到“观众看不到提示词,而我能看到”的效果,就只能把提示词展示到额外的显示设备上。我们每个人都有智能手机,因此我就想到了把智能手机做为显示提示词的设备。

基于这个想法,我开发出了一个桌面应用,这个应用提供了一个内嵌的Web服务器,提供了“获取当前PPT页面备注文字”以及“翻页”等功能的接口,并且提供了一个调用这些接口的网页;这样,只要在手机上访问这个网页,就可以通过手机来获取提示词,也可以通过手机来切换PPT的翻页。

下图是我使用这个提词应用实际工作的场景:


我的提词器实际工作场景

这个应用使用.NET 5/.NET Core开发,但是思路是不局限于语言的,其他编程语言的开发者也可以使用你习惯的语言来开发。

我的应用主要使用了两个技术,一个是在WinForm程序中内嵌Web服务器,另一个就是通过代码控制PowerPoint文档。我下面将对它们分别做讲解。


.NET 内嵌Web服务器技术

.NET中可以使用Kestrel实现内嵌Web服务器,而Kestrel就是ASP.NET Core项目默认的Web服务器。由于Kestrel只是一个NuGet包而已,因此可以把它装到任何.NET项目上,比如控制台、WinForm、WPF、Xamarin等。

其实所谓的ASP.NETCore项目本质上也只是一个装了Kestrel等相关包的控制台程序而已。这里演示在WinForm项目中的用法,其他类型项目操作步骤都差不多:

1、首先创建一个WinForm项目,然后在项目根目录下创建名字为wwwroot的文件夹,这个文件夹用来放html、js、css等静态文件。

2、在项目的csproj文件中增加如下配置:

<None Update="wwwroot\**">
       <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

这段配置的作用是:项目构建的时候,会把wwwroot目录下所有的内容都复制到输出目录下。

3、在wwwroot目录下创建index.html文件。

4、通过Nuget安装:Microsoft.AspNetCore.Owin

Install-Package Microsoft.AspNetCore.Owin

5、在主窗口中声明

private IWebHost host;

在窗口的构造函数中添加如下内容:

host = new WebHostBuilder()
  .UseKestrel()
  .UseUrls("http://*:80")
  .Configure(Configure)
  .Build();
host.RunAsync();

其中"http://*:80" 表示网站监听所有网卡(这样就可以通过其他设备访问内嵌的网站了),并且通过80端口提供服务。要确保计算机中的防火墙中开启了对应端口的访问权限。

在需要关闭服务器或者窗口关闭的时候执行如下代码,否则程序不会正常退出:

host.StopAsync();
host.WaitForShutdown();

然后声明如下方法:

public void Configure(IApplicationBuilderapp)
{
       app.UseDefaultFiles();
       app.UseStaticFiles();
       app.Run(async(context) =>
       {
              varrequest = context.Request;
              varresponse = context.Response;
              stringpath = request.Path.Value;
              if(path == "/report")
              {
                     response.StatusCode= 200;
                     awaitresponse.WriteAsync("OK");
              }
              else
              {
                     response.StatusCode= 404;
              }
       });
}

其中app.UseDefaultFiles()表示启用对于index.html等默认文档的支持;app.UseStaticFiles()表示把wwwroot提供为静态文件夹。

app.Run()中的代码意思为:如果用户访问了/report这个路径,则输出OK,否则就响应码为404。


代码控制PowerPoint文档

代码需要实现读取PowerPoint页面的备注文字以及翻页等功能,这需要使用Office Automation技术,也就是通过代码调用Office的COM接口,当前前提条件就是计算机上必须安装PowerPoint软件。微软官方推荐的在.NET中访问Office的方法就是在Visual Studio中通过COM引用生成Office的Interop程序集,也就是所谓的“Early Binding”。

这种方式的优点就是一切对象都是强类型的,所以代码编写比较方便。而缺点就是和特定Office版本绑定,必须注意开发的时候的Office绑定,必须用尽可能低的版本的Office进行开发。

不知道是我本地环境的原因还是.NET 5对于这种方式支持不成熟,我在.NET 5项目通过COM引用方式使用的时候,一直出现“MsoTriState在未被引用的程序集中定义。必须添加对程序集office的引用”的编译错误,如下图:
Figure3编译错误

也可以使用Late Binding方式操作,也就是通过dynamic这种方式进行COM接口的访问。这种方式的优点是不依赖于特性Office版本,缺点就是全都是弱类型调用,因此需要查询文档,开发效率比较低。       

我发现一个开源项目NetOffice(https://netoffice.io/),它仍然是强类型的,但是不依赖于特定的Office版本。最大的遗憾就是它目前的.NET Standard版本的开发正在进行,所以目前的版本仍然不支持.NET Core。      

经过比较,我只能选择Late Binding这种方式来进行了。由于Com的复杂性,特别是“引用计数”这种比较古老的资源管理技术的复杂性,导致晚绑定的对象回收要十分注意,否则会导致Office无法退出。

我封装了一个简单的库Zack.ComObjectHelpers,可以简化Com对象资源的回收。这个库的Nuget安装命令是:

Install-PackageZack.ComObjectHelpers

然后使用其中的COMReferenceTracker类进行COM引用的管理:打开文档创建一个COMReferenceTracker对象,在每一步可能返回Com对象的地方,都用T方法进行资源回收,操作完成后调用Dispose。       

如下的代码就是打开一个PPT文档,然后进入演示模式的代码:

private dynamic presentation;
private COMReferenceTracker comRefTracker =new COMReferenceTracker();
 
private void Form1_Closed(object sender,EventArgs e)
{
       this.comRefTracker.Dispose();
}
 
private dynamic T(dynamic comObj)
{
       returnthis.comRefTracker.T(comObj);
}
 
private void MiOpen_Click(object sender,System.EventArgs e)
{
       stringfilename = "d:/1.pptx";
       dynamicpptApp = T(PowerPointHelper.CreatePowerPointApplication());
       pptApp.Visible= true;
       dynamicpresentations = T(pptApp.Presentations);
       this.presentation= T(presentations.Open(filename));
       T(this.presentation.SlideShowSettings).Run();
}

C#操作Office Automation的文档、资料比较少,不过由于COM对象本身是跨语言的,而VBA操作Office Automation的资料非常多,因此完全参考VBA操作的资料即可。

比如下面的代码就是我仿照网上搜索“VBA 读取PowerPoint备注”的代码改造成C#语法而成的“读取当前PowerPoint页面的备注”代码:

dynamic notesPage =T(T(T(T(presentation.SlideShowWindow).View).Slide).NotesPage);
notesText = GetInnerText(notesPage);
 
private string GetInnerText(dynamic part)
{
       StringBuildersb = new StringBuilder();
       dynamicshapes = T(T(part).Shapes);
       intshapesCount = shapes.Count;
       for(int i = 0; i < shapesCount; i++)
       {
              dynamicshape = T(shapes[i + 1]);
              vartextFrame = T(shape.TextFrame);
              if(textFrame.HasText == -1)//MsoTriState.msoTrue==-1
              {
                     stringtext = T(textFrame.TextRange).Text;
                     sb.AppendLine(text);
              }
              sb.AppendLine();
       }
       returnsb.ToString();
}

手机提词器应用代码


上面已经把整个应用的最核心代码介绍了,想了解整个项目的代码请访问项目的github页面https://github.com/yangzhongke/PhoneAsPrompter

手机控制电脑中视频播放

用“应用中内嵌Web服务器”技术,我还实现了一个“手机控制电脑中视频播放”的应用,可以通过手机控制电脑中的视频播放器进行暂停、播放、调节音量、快进快退等功能,甚至可以进一步开发完成切换直播电视频道等功能。代码也放到了上面github页面的VideoRemoteController项目中。

git:https://github.com/yangzhongke/PhoneAsPrompter

- EOF -

推荐阅读  点击标题可跳转
.NET 某医院HIS系统 CPU爆高分析.NET 中两分钟集成敏感词组件.NET 生成小程序码


看完本文有收获?请转发分享给更多人

推荐关注「DotNet」,提升.Net技能 

点赞和在看就是最大的支持❤️

: . Video Mini Program Like ,轻点两下取消赞 Wow ,轻点两下取消在看

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存